#ifndef G3_RES_HANDLE_MGR_H
#define G3_RES_HANDLE_MGR_H

#include "res_handle.h"

#include <vector>
#include <string>
#include <set>
#include <map>
#include <time.h>
#include <boost/shared_ptr.hpp>

namespace G3 {

class RESOURCE_STATS {
	public:
		RESOURCE_STATS (time_t *aLastAccess, int *aLockCount, int aPriority = 0, long aSize = 0, uint aResID = 0);

		int priority;
		int *lock_count;
		time_t *last_access;
		long size_in_memory;
		uint resource_id;

		void SetAccessed ();
		bool IsLocked () const;

		bool operator< (const RESOURCE_STATS &aStats) const;
};

// RType has to be derived from RESOURCE_BASE
template <typename RType>
class HANDLE_MANAGER: public LOGGABLE {
	private:
		std::vector<boost::shared_ptr<RType> > data_list;
		std::vector<HANDLE<RType> > handles;
		std::vector<uint> free_handles;
		std::multiset<RESOURCE_STATS> stats;
		long memory_limit;
		long memory_usage;

	public:
		HANDLE_MANAGER () {
			// 1 MB
			memory_limit = 1024000;
			memory_usage = 0;
		}

		~HANDLE_MANAGER () {
		    for (int i=0; i<(int)data_list.size (); i++) {
		        if (data_list [i])
                    data_list [i]->Release ();
		    }

            data_list.clear ();
            handles.clear ();
            free_handles.clear ();
            stats.clear ();
		}

		std::multiset<RESOURCE_STATS>::iterator FindStat (uint aResID) {
			std::multiset<RESOURCE_STATS>::iterator it = stats.begin ();

			while (it != stats.end ()) {
				if (it->resource_id == aResID)
					return it;

				it++;
			}
			return stats.end ();
		}

//==============================================================================
// Memory limit manipulation
//==============================================================================
		void SetMemoryLimit (const long &aLimit) {
			memory_limit = aLimit;
		}

		long GetMemoryLimit () {
			return memory_limit;
		}

		long GetMemoryUsage () {
			return memory_usage;
		}

//==============================================================================
// Discards low priority, small and/or seldom used resources
//==============================================================================
		void DiscardUnused (const long &aSize) {
			std::multiset<RESOURCE_STATS>::iterator it = stats.begin ();
			uint rID = 0;

			// Nothing to discard or there's no need for discarding anything?
//			if (Stats.empty () || MemoryUsage + aSize < MemoryLimit) {
				return;
//            }

            // TODO:: FixMe

			// Loop while there's enough free memory
			while (memory_usage + aSize > memory_limit) {
				// Couldn't erase anything?
				if (it == stats.end ()) {
				    if (log)
                        log->Report ("WARNING! HANDLE_MANAGER::DiscardUnused: Short of memory, %ld bytes is not enough (should be at least %ld bytes)..\n", memory_limit, memory_usage);
					return;
				}

				// Can't erase this one cause it's locked?
				if (it->IsLocked ()) {
					it++;
					continue;
				}

				rID = it->resource_id;

				Assert (rID < data_list.size (), "HANDLE_MANAGER::DiscardUnused: Invalid ResourceID\n");

				// Update memory usage
				memory_usage -= data_list [rID]->GetSize ();
				// Erase the least used resource from the DataList
				data_list [rID]->Release ();
				data_list [rID]->Clear ();
				data_list.erase (data_list.begin () + rID);
				stats.erase (it);
			}
		}

//==============================================================================
// Allocates memory for a new element or reuses an old one
//
//	NOTE::  Only allocates memory for the empty element
//			so that MemoryUsage may peak over MemoryLimit
//==============================================================================
		boost::shared_ptr<RType> Acquire (HANDLE<RType> &aHandle, int aPriority = 0) {
			uint index = 0;
			boost::shared_ptr<RType> temp (new RType ());
			long tSize = temp->GetSize ();

			// Free some memory for the new one
			DiscardUnused (tSize);

			if (free_handles.empty ()) {
				index = handles.size ();
				if (!aHandle.Init (index)) {
					if (log)
						log->Report ("ERROR! HANDLE_MANAGER::Acquire: Failed to initialize a handle with index %d..\n", index);
					return boost::shared_ptr<RType>();
				}

				data_list.push_back (temp);
				handles.push_back (aHandle);

				// There's at least 1 free handle?
			} else {
				index = free_handles.back ();
				if (!aHandle.Init (index)) {
					if (log)
						log->Report ("ERROR! HANDLE_MANAGER::Acquire: Failed to initialize a handle with index %d..\n", index);
					return boost::shared_ptr<RType>();
				}

				free_handles.pop_back ();
				handles [index] = aHandle;
			}

			// Add it to resource stats
			stats.insert (RESOURCE_STATS (&data_list [index]->last_access,
										  &data_list [index]->lock_count,
										  aPriority, tSize, index));

			return data_list [index];
		}

//==============================================================================
// Releases a handle so that it can later be reused
//==============================================================================
		bool Release (const HANDLE<RType> &aHandle) {
		    if (aHandle.IsNull ())
                return false;

			std::multiset<RESOURCE_STATS>::iterator it;
			uint index = aHandle.GetIndex ();

			if (index >= data_list.size ()) {
				if (log)
					log->Report ("ERROR! HANDLE_MANAGER::Release (%d): Invalid handle (%d is the maximum valid index)..\n", index, data_list.size ());
				return false;
			}

			if (handles [index].GetMagic () != aHandle.GetMagic ()) {
				if (log)
					log->Report ("ERROR! HANDLE_MANAGER::Release (%d): Invalid handle (magic numbers don't match)..\n", index);
				return false;
			}

			// Can't release a locked resource
			if (data_list [index]->IsLocked ())
				return false;

			handles [index].SetNull ();
			free_handles.push_back (index);

			// Erase the resource stat
			it = FindStat (index);
			if (it != stats.end ())
				stats.erase (it);

			return true;
		}

//==============================================================================
// Gets pointer to data that the Handle refers to
//==============================================================================
		boost::shared_ptr<RType> GetData (const HANDLE<RType> &aHandle) {
			Assert (!aHandle.IsNull (), "HANDLE_MANAGER::GetData (): Trying to get the data of a NULL handle");

			uint index = aHandle.GetIndex ();

			if (index >= data_list.size () ||
				handles [index].GetMagic () != aHandle.GetMagic ()) {
					if (log)
						log->Report ("ERROR! HANDLE_MANAGER::GetData (%d): Invalid handle..\n", index);
					return boost::shared_ptr<RType> ();
			}

			// We have accessed this resource
			data_list [index]->SetAccessed ();

			return data_list [index];
		}

//==============================================================================
// Locking / Unlocking / Lock status functions
//==============================================================================
		bool Lock (const HANDLE<RType> &aHandle) {
			Assert (!aHandle.IsNull (), "HANDLE_MANAGER::Lock (): Trying to get the data of a NULL handle");

			uint index = aHandle.GetIndex ();

			if (index >= data_list.size () ||
				handles [index].GetMagic () != aHandle.GetMagic ()) {
					if (log)
						log->Report ("ERROR! HANDLE_MANAGER::Lock (%d): Invalid handle..\n", index);
					return false;
			}

			data_list [index]->Lock ();
			return true;
		}

		bool UnLock (const HANDLE<RType> &aHandle) {
			Assert (!aHandle.IsNull (), "HANDLE_MANAGER::UnLock (): Trying to get the data of a NULL handle");

			uint index = aHandle.GetIndex ();

			if (index >= data_list.size () ||
				handles [index].GetMagic () != aHandle.GetMagic ()) {
					if (log)
						log->Report ("ERROR! HANDLE_MANAGER::UnLock (%d): Invalid handle..\n", index);
					return false;
			}

			data_list [index]->UnLock ();
			return true;
		}

		bool IsLocked (const HANDLE<RType> &aHandle) const {
			Assert (!aHandle.IsNull (), "HANDLE_MANAGER::IsLocked (): Trying to get the data of a NULL handle");

			uint index = aHandle.GetIndex ();

			if (index >= data_list.size () ||
				handles [index].GetMagic () != aHandle.GetMagic ()) {
					if (log)
						log->Report ("ERROR! HANDLE_MANAGER::IsLocked (%d): Invalid handle..\n", index);
					return false;
			}

			return data_list [index]->IsLocked ();
	}

//==============================================================================
// Dumps the contents of the whole handle manager into the log
//==============================================================================
		void Dump () {
			int i;

			if (log) {
				log->Report ("Dumping DataList:\n");
				if (!data_list.empty ()) {
					for (i=0; i<(int)data_list.size (); i++)
						log->Report (" Reference: 0x%X\n", &data_list [i]);
				} else {
					log->Report ("<Empty>\n");
				}

				log->Report ("\nDumping Handles:\n");
				if (!handles.empty ()) {
					for (i=0; i<(int)handles.size (); i++)
						log->Report (" %d\n", handles [i].GetMagic ());
				} else {
					log->Report ("<Empty>\n");
				}

				log->Report ("\nDumping FreeHandles:\n");
				if (!free_handles.empty ()) {
					for (i=0; i<(int)free_handles.size (); i++)
						log->Report (" %d\n", free_handles [i]);
				} else {
					log->Report ("<Empty>\n");
				}
			}
		}
};

template<class T>
class RESOURCE_MANAGER: public LOGGABLE {
    public:
        RESOURCE_MANAGER () {
        }

        virtual ~RESOURCE_MANAGER () {
            Clear ();
        }

        virtual std::string& GetName (HANDLE<T> handle) {
            boost::shared_ptr<T> p = handle_mgr.GetData (handle);
            static std::string emptyString;

            if (p)
                return p->Name;

            return emptyString;
        }

        virtual HANDLE<T> GetHandle (const std::string &filename) = 0;

        virtual boost::shared_ptr<T> GetPointer (HANDLE<T> handle) {
            if (handle.IsNull ())
                return boost::shared_ptr<T>();

            return handle_mgr.GetData (handle);
        }

        virtual void Delete (HANDLE<T> handle) = 0;

        virtual void Clear () {
        }

        virtual void Dump () {
        }

    protected:
        std::map<std::string, HANDLE<T> > name_index;
		HANDLE_MANAGER<T> handle_mgr;
};

}

#endif
